2025-10-07
Computervidenskabelig disciplin, der beskæftiger sig med, hvordan en computer kan forstå og producere menneskeligt sprog.
Computationel tekstanalyse handler om at bruge computerens kapacitet til at finde mønstre, strukturer og betydninger i store mængder tekst, som ville være umulige for mennesker at behandle manuelt.
Forestil dig, at du skal lære et barn at genkende forskellige dyrearter. Den mest oplagte måde ville være at vise barnet billeder af dyr, hvor du hver gang siger: “Det her er en hund”, “Det her er en kat”, osv. Efter at have set mange eksempler kan barnet begynde at genkende nye dyr, det ikke har set før.
Forestil jer, at du står med en filmanmeldelse og vil afgøre, om den er positiv eller negativ. Naive Bayes spørger: “Givet de ord, jeg ser i denne anmeldelse, hvad er sandsynligheden for, at den tilhører hver kategori?”
Dette tager afsæt i Bayes’ Theorem/-sætning:
\[ P(C \mid D) = \frac{P(D \mid C) \times P(C)}{P(D)} \]
Sætningen fortæller os, hvordan vi kan vende en betinget sandsynlighed om. Hvis vi kalder vores kategorier for \(C\) (“positiv” eller “negativ”) og vores dokument for \(D\) (repræsenteret ved de ord, det indeholder), så siger Bayes’ sætning:
\[ P(C \mid D) = \frac{P(D \mid C) \times P(C)}{P(D)} \]
Den naive antagelse er at vi går ud fra uafhængighed mellem ord. I virkeligheden er ord i en tekst åbenlyst ikke uafhængige af hinanden.
Naive Bayes antager, at alle ord er betingelsesvist uafhængige givet kategorien, udtrykt som:
\[ P(D \mid C) = P(w_1, w_2, \ldots, w_n \mid C) = P(w_1 \mid C) \times P(w_2 \mid C) \times \cdots \times P(w_n \mid C) \]
hvor \(w_1, w_2, \ldots, w_n\) er de individuelle ord i dokumentet. Denne antagelse er objektivt forkert, men den fungerer stadig i praksis, fordi den gør beregningerne håndterbare.
Forstil jer at vi har trænet en model på tusindvis af filmanmeldelser, og vi ser denne korte anmeldelse: "Fantastisk film. Elsker!".
positiv (\(P\)) og negativ (\(N\)).Nu forestiller dig i stedet, at du giver barnet en stor kasse med forskellige legetøjsting uden at fortælle noget om dem. Barnet vil naturligt begynde at sortere tingene i bunker baseret på ligheder, det selv opdager: det kunne fx lægger alle røde ting sammen, eller alle ting med hjul. Barnet opdager strukturer uden vejledning.
Hvor Naive Bayes arbejder med sandsynligheder og kræver labelet data, virker \(k\)-means clustering fundamentalt anderledes.
Vi skal forestille os at vi har tusindvis af dokumenter spredt ud i et højdimensionalt rum, hvor hver dimension repræsenterer et ord eller en tekstfunktion. \(K\)-means forsøger at finde naturlige grupperinger i dette rum ved at minimere afstanden mellem dokumenter i samme gruppe.
1: Hvert dokument er repræsenteret som en vektor i et højdimensionalt rum. Hvis vi bruger en bag-of-words repræsentation med \(N\) unikke ord, bliver hvert dokument en vektor \(d = (d_1, d_2, \cdots , d_n)\), hvor \(d_n\) er en vægt.
\[ \text{distance}(d, d') = \sqrt{\sum_{i=1}^{N} (d_i - d'_i)^2} \]
Dette er den direkte linje-afstand mellem to punkter i rummet, generaliseret til \(N\) dimensioner.
\[ \text{WCSS} = \sum_{j=1}^{k} \sum_{d \in C_j} \lVert d - \mu_j \rVert^2 \]
\(C_j\) er \(j\)’ende cluster. \(\mu_j\) er centroiden (gennemsnittet) af alle dokumenter i cluster \(j\), bestemt som \(\mu_j = \frac{1}{|C_j|} \sum_{d \in C_j} d\). \(\lVert d - \mu_j \rVert^2\) er den kvadrerede euklidiske afstand mellem dokument \(d\) og centroiden. Intuitionen er at vi vil have dokumenter i samme cluster til at ligge tæt på hinanden.
Forestil jer, at vi har repræsenteret tre artikler baseret på deres ordfrekvenser for ordene “politik” og “sport”.
Et centralt spørgsmål i \(k\)-means er, hvordan vi vælger antallet af clusters \(k\).
WCSS (Within-Cluster Sum of Squares)
|
1000|●
|
800| ●
|
600| ●
| ╲
400| ●
| ╲
200| ●___●___●___●___●
|
0|_________________________________ k
1 2 3 4 5 6 7 8 9
Stejl fald | Fladt plateau
(stor gevinst)| (lille gevinst)
↑
Optimal k ≈ 4
Altså, for at kunne clustre dokumenter skal vi forstå hvordan vi matematisk kan udtrykke hvor meget eller lidt to dokumenter ligner hinanden.
Hvert dokument bliver repræsenteret som en vektor i et højdimensionelt rum. Hvis vores corpus har \(3000\) forskellige ord, bliver hvert dokument en vektor med \(3000\) tal der viser hvor ofte hvert ord forekommer. For to dokumenter, \(f_i\) og \(f_j\), kan vi beregne forskellige distance measures.
Euclidean distance (\(L2\)): Tænk på det som at måle den rette linje mellem to punkter i rummet. Hvis dokument A har ordet “demokrati” \(10\) gange og dokument B har det \(3\) gange, bidrager dette ord med \((10-3)^2 = 49\) til den samlede distance. Læg alle sådanne bidrag sammen for alle ord, og tag kvadratroden. \[ L_2(f_i, f_j) = \sum_{r=1}^{m} (f_{ir} - f_{jr})^2 \]
Manhattan distance (\(L1\)): I stedet for at kvadrere forskellene, tager vi den absolutte værdi af hver forskel og lægger dem sammen. Det giver et andet billede af distance end fugleflugt-linjen (Euclidean).
\[ CD(f_i, f_j) = 1 - \frac{\sum_{r=1}^{m} f_{ir} f_{jr}}{\sqrt{\sum_{r=1}^{m} f_{ir}^2} \, \sqrt{\sum_{r=1}^{m} f_{jr}^2}} \]
Jeg elsker politik
Jeg elsker ikke politik
Jeg elsker politik, når folk råber i munden på hinanden
Jeg elsker politik, når folk råber i munden på hinanden. Først da kan jeg mærke, hvor passionerede politikerne er.
Tim valgte kort før Allan.
Tekstdata har altid høj dimensionalitet (mange variable)
text vectorization” til at referere til teknikker, der konverterer tekster til matematiske repræsentationer (datastrukturer, som man kan foretage beregninger på)Bag-of-WordsTekst 1: Katten spiser fisk
Tekst 2: Hunden spiser kød
Tekst 3: Katten og hunden leger
| Dokument | katten | spiser | fisk | hunden | kød | og | leger |
|---|---|---|---|---|---|---|---|
| Tekst 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
| Tekst 2 | 0 | 1 | 0 | 1 | 1 | 0 | 0 |
| Tekst 3 | 1 | 0 | 0 | 1 | 0 | 1 | 1 |
Document-term matrixTekst 1: Jeg elsker spam
Tekst 2: Jeg kan ikke fordrage spam
term frequency-inverse document frequencyBygger på en intuitiv idé om hvad der gør ord vigtige.
\[ \text{tf-idf} = \text{tf}(t, d) \times \text{idf}(t, D) \]
\(\text{tf}(t, d)\) er antal gange ord \(t\) er nævnt i dokument \(d\)
\(\text{idf}(t, D) = \log \left( \frac{D}{|\{ d \in D : t \in d \}|} \right)\) er logaritmen af det samlede antal dokumenter, \(D\), divideret med antallet af dokumenter, der indeholder termen
Med andre ord:
Tekst 1: Katten spiser fisk og katten spiser mælk
Tekst 2: Hunden spiser kød og hunden løber hurtigt
Tekst 3: Katten leger og hunden leger sammen
Totalt antal dokumenter: \(N = 3\)
\[ \text{tf}(t, d) \times \text{idf}(t, D) \]
\[ \text{tf}(t, d) \times \text{idf}(t, D) \]
\[ \text{tf}(t, d) \times \text{idf}(t, D) \]
| Dokument | katten | spiser | fisk | og | mælk | hunden | kød | løber | hurtigt | leger | sammen |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Tekst 1 | 0.044 | 0.044 | 0.060 | 0.000 | 0.060 | 0 | 0 | 0 | 0 | 0 | 0 |
| Tekst 2 | 0 | 0.025 | 0 | 0.000 | 0 | 0.050 | 0.068 | 0.068 | 0.068 | 0 | 0 |
| Tekst 3 | 0.029 | 0 | 0 | 0.000 | 0 | 0.029 | 0 | 0 | 0 | 0.159 | 0.080 |
"og" optræder i alle tre dokumenter, hvilket giver det en IDF på præcis nul. Det betyder at uanset hvor mange gange “og” optræder i et dokument, så bliver dets TF-IDF værdi altid nul. Systemet har effektivt lært at ignorere dette almindelige bindeord, præcis som vi mennesker instinktivt gør når vi læser.ordtabelTekst 1: Vi er utroligt beærede
Tekst 2: Vi vil gerne dele prisen med alle
Tekst 3: Det skal vi have gjort op med.
| ID | Word | POS | Order | doc:ID |
|---|---|---|---|---|
| 0 | Vi | PRON | 1 | 1 |
| 1 | er | AUX | 2 | 1 |
| 2 | utroligt | ADV | 3 | 1 |
| 3 | beærede | VERB | 4 | 1 |
| 4 | Vi | PRON | 1 | 2 |
| 5 | vil | AUX | 2 | 2 |
| 6 | gerne | ADV | 3 | 2 |
| 7 | dele | VERB | 4 | 2 |
| 8 | prisen | NOUN | 5 | 2 |
| 9 | med | ADP | 6 | 2 |
graf/netværk| ID | From | To |
|---|---|---|
| 1 | Vi | er |
| 1 | er | utroligt |
| 1 | utroligt | beærede |
ID’et (1) angiver at alle disse forbindelser tilhører den samme sætning.
Pas på medUkritisk teknologioverførsel:
Preprocessing-råd kommer primært fra supervised learning (klassifikation) og anvendes ukritisk på unsupervised metoder (topic models, scaling)
… men disse to tilgange har forskellige mål og evalueringsmetrikker.
| Trin | Beskrivelse | Potentielle problemer |
|---|---|---|
| P - Punctuation | Fjern tegnsætning | Kan være informativ i visse domæner (f.eks. hashtags) |
| N - Numbers | Fjern tal | “Section 423” kan være substantivt vigtig |
| L - Lowercasing | Små bogstaver | “Rose” (navn) vs “rose” (blomst) |
| S - Stemming | Reduktion til ordstamme | “partying” og “parties” → “parti” |
| W - Stopwords | Fjern almindelige ord | Ingen guldstandard-liste; 100–1000 ord |
| 3 - n-grams | Inkluder 2- og 3-grams | “national defense” vs “national debt” |
| I - Infrequent | Fjern sjældne termer | Typisk <1% document frequency |
Opdeling af tekst til enkelte tokens (ordenheder)
“Politiet har givet borgerne råd” -> [‘Politiet‘, ‘har‘, ‘givet‘, ‘borgerne‘, ‘råd‘]
Typisk referer tokenization til at opdele tekster i enkeltord, men afhængig af formål, kan det være relevant at se på tokens som “ordpar” (bigrams) eller “ordsamlinger” (n-grams).
’Politiet har givet borgerne råd‘ -> [(‘Politiet‘, ‘har‘), (‘har‘, ‘givet‘), (‘givet‘, ‘borgerne‘), (‘borgerne‘, ‘råd‘)]
Typisk vil man ikke behandle ord med forskellig gradbøjning som forskellige ord. Simpleste måde at ensrette er at omdanne til ordstammen.
Der er flere algoritmer til at lave stemming—og de ikke altid enige!
Konverterer ordet til grammtiske stamme (alternativt til stemming)
Lemmatizere er af nødvendighed sprogafhængige
Mange ord indgår i tekst uanset hvad teksten handler om (bindeord, stedord, forholdsord o.l.)
Opdeler ord i ordklasser: navneord, udsagnsord, tillægsord osv.
’Politiet har givet borgerne råd‘ -> ‘Politiet_NOUN har_AUX givet_VERB borgerne_NOUN råd_NOUN‘
Dependency parsing udleder sætningskonstruktion: grundled, udsagnsled, genstandsled.
’Politiet har givet borgerne råd‘ -> ‘Politiet_nsubj har_aux givet_ROOT borgerne_obj råd_obj‘
En sprogmodel (“language model”) er kort sagt en model til at forudsige ord. Lidt forsimplet er en sprogmodel en model, som er trænet til at genkende ordtyper, entiteter og sætningskonstruktion, som derved kan prædiktere disse informationer i tekst (for det meste inden for samme sprog).
Forestil jer, at vi skal forudsige det næste ord i sætningen "Jeg går i...".
"skole", "biografen" eller "parken" er sandsynlige fortsættelser, mens "grøn" eller "sov" er usandsynlige.Næsten alle NLP-opgaver kan reduceres til eller drage fordel af sprogmodeller.
Part-of-speech tagging spørger: “Hvad er den mest sandsynlige ordklasse for dette ord i denne kontekst?”Named entity recognition spørger: “Hvad er den mest sandsynlige entitetstype for denne ordsekvens?”Maskinoversættelse kan ses som: “Hvad er den mest sandsynlige sætning i målsproget givet kildesætningen?”Computere kan kun arbejde med tal, men sprog er symboler med komplekse relationer og betydninger.
Problem 1: Ingen semantisk information. Afstanden mellem "velfærd" og "uddannelse" er nøjagtig den samme som mellem "velfærd" og "forsvar". Computeren seks totalt forskellige ord uden nogen relation. Men intuitivt hører velfærd og uddannelse sammen i den sociale politiske sfære, mens forsvar er noget helt andet.
Problem 2: Dimensionalitetseksplosion. Et realistisk ordforråd har \(50,000-100,000\) ord. Det betyder vektorer med \(50,000-100,000\) dimensioner, hvor \(99.999\%\) er nuller. Ekstremt ineffektivt og gør beregninger umulige.
Problem 3: Ingen generalisering. Hvis modellen har lært at "regeringen øgede velfærdsudgifter" er positivt korreleret med socialdemokratisk politik, ved den intet om "administrationen forhøjede sociale udgifter", selvom sætningerne er semantisk identiske. Hvert ord behandles som totalt uafhængigt.
“You shall know a word by the company it keeps”: et ords betydning er defineret af de kontekster, det optræder i.
Vi har brug for er en repræsentation, der fanger, at ord der optræder i lignende kontekster har lignende betydninger: word embeddings.
Ideen bag word embeddings er at mappe hvert ord til et punkt i et kontinuerligt, høj-dimensionalt rum, hvor geometrisk afstand reflekterer semantisk lighed.
Et lille corpus om politik:
Vi kan tælle hvilke ord der optræder sammen (inden for et vindue på \(±2\) ord).
Kontekst for “regeringen”:
{forøgede, styrkede, velfærdsudgifterne, forsvarsinvesteringer}Kontekst for “oppositionen”:
{kritiserede, roste, velfærdspolitikken, uddannelsesindsatsen}Kontekst for “administrationen”:
{reducerede, forsvarsbudgettet}Repræsenteret som co-occurrence matrix. Hver række er et ord, hver kolonne er et kontekstord, og tallet er hvor mange gange de optræder sammen:
| forøgede | reducerede | kritiserede | styrkede | roste | velfærd* | forsvar* | uddannelse* | |
|---|---|---|---|---|---|---|---|---|
| regeringen | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 0 |
| oppositionen | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 |
| administrationen | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 |
Men disse vektorer er stadig meget sparse (mange nuller) og høj-dimensionelle …
Skip-gram modellen siger: “Givet et ord, forudsig dets kontekst.”
Ideen er, at ord med lignende betydninger optræder i lignende kontekster. Hvis vi ofte ser “hund” i nærheden af “gøede”, “bold”, “gå tur”, vil vi sandsynligvis også se “kat” i lignende kontekster (måske “miavede”, “legetøj”, “kæle”).
Lad os forestille os, at efter træning på et stort politisk corpus har vi lært disse embeddings (forenklet til 2D):
| dim1 (økonomisk venstre-højre) | dim2 (institutionel magt) | |
|---|---|---|
| velfærd | -0.8 | 0.1 |
| uddannelse | -0.6 | 0.2 |
| sundhed | -0.7 | 0.3 |
| forsvar | 0.3 | 0.4 |
| sikkerhed | 0.4 | 0.5 |
| regering | 0.0 | 0.9 |
| opposition | 0.0 | 0.7 |
| minister | 0.1 | 0.8 |
| borgmester | -0.1 | 0.6 |
Fra disse dimensioner kan vi beregne semantisk lighed.
Cosinus-ligheden mellem to vektorer:
\[ \text{similarity}(\mathbf{v}_1, \mathbf{v}_2) = \frac{\mathbf{v}_1 \cdot \mathbf{v}_2}{\|\mathbf{v}_1\| \times \|\mathbf{v}_2\|} \]
Vektorerne:
\[ \mathbf{v}_1 = [-0.8, 0.1] \quad \text{og} \quad \mathbf{v}_2 = [-0.6, 0.2] \]
Dot product:
\[ \mathbf{v}_1 \cdot \mathbf{v}_2 = (-0.8) \times (-0.6) + 0.1 \times 0.2 = 0.48 + 0.02 = 0.50 \]
Vektorlængder:
\[ \|\mathbf{v}_1\| = \sqrt{0.64 + 0.01} = \sqrt{0.65} \approx 0.806 \]
\[ \|\mathbf{v}_2\| = \sqrt{0.36 + 0.04} = \sqrt{0.40} \approx 0.632 \]
Cosinus-lighed:
\[ \text{similarity} = \frac{0.50}{0.806 \times 0.632} = \frac{0.50}{0.509} \approx 0.982 \]
Dette viser at “velfærd” og “uddannelse” har meget høj semantisk lighed (tæt på 1).
Vektorerne:
\[ \mathbf{v}_1 = [-0.8, 0.1] \quad \text{og} \quad \mathbf{v}_2 = [0.3, 0.4] \]
Dot product:
\[ \mathbf{v}_1 \cdot \mathbf{v}_2 = (-0.8) \times 0.3 + 0.1 \times 0.4 = -0.24 + 0.04 = -0.20 \]
Vektorlængder:
\[ \|\mathbf{v}_1\| \approx 0.806 \]
\[ \|\mathbf{v}_2\| = \sqrt{0.09 + 0.16} = \sqrt{0.25} = 0.5 \]
Cosinus-lighed:
\[ \text{similarity} = \frac{-0.20}{0.806 \times 0.5} = \frac{-0.20}{0.403} \approx -0.496 \]
Dette viser at “velfærd” og “forsvar” har negativ semantisk lighed (modsat rettede politiske koncepter).
y
│
0.4 •forsvar •skat
│
0.3 •uddannelse
│
0.2 •velfærd
│
└──────────────────────────────── x
-0.6 -0.5 0.7 0.8
Hvad betyder det så hvis vi har 300 dimensioner?
ord_vector = [
0.32, # dimension 1
-0.18, # dimension 2
0.91, # dimension 3
-0.45, # dimension 4
0.72, # dimension 5
... # ... 295 flere dimensioner
-0.23 # dimension 300
]
Dimensionerne er ikke foruddefinerede som “politisk orientering” eller “sport”. De emergerer under træningen. Modellen opdager selv, hvilke akser der er nyttige til at skelne ord fra hinanden.
En hypotetisk dimension
Dimension X, værdier:
king : 0.823
queen : -0.791
man : 0.756
woman : -0.742
boy : 0.612
girl : -0.598
prince : 0.834
princess : -0.808
father : 0.701
mother : -0.688
Mønster: Dimension kunne se ud til at fange køn. Positive værdier = maskulin, negative = feminin.
En anden hypotetisk dimension:
Dimension Y værdier:
king : 0.891
queen : 0.876
prince : 0.723
princess : 0.712
peasant : -0.634
servant : -0.612
Mønster: Dimensionen fanger måske social status eller royal status.
Men de fleste dimensioner er ikke fortolkelige!
"da_core_news_sm": "Ingen vectors"
"da_core_news_md": "96 dimensioner"
"da_core_news_lg": "300 dimensioner"
"BERT": "768 dimensioner"
"GPT-3": "12,288 dimensioner"
Ofte vil vi visualisere embeddings, men vi kan ikke visualisere 300 dimensioner. Så reducerer vi til 2 (eller 3) dimensioner.
PCA finder de 2 retninger i det 300-dimensionale rum, hvor dataene varierer mest.import spacy
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import numpy as np
# python3 -m spacy download da_core_news_md
nlp = spacy.load("da_core_news_md")
# Hent vektorer for politiske ord
words = ["velfærd", "uddannelse", "sundhed", "forsvar", "politi",
"skat", "regering", "opposition"]
vectors = []
for word in words:
doc = nlp(word)
if doc[0].has_vector:
vectors.append(doc[0].vector)
vectors = np.array(vectors) # Shape: [8 words × 300 dimensions]
# Reducer til 2 dimensioner
pca = PCA(n_components=2)
vectors_2d = pca.fit_transform(vectors) # Shape: [8 words × 2 dimensions]male_words = nlp("han mand far dreng")
male = np.mean([token.vector for token in male_words], axis=0)
female_words = nlp("hun kvinde mor pige")
female = np.mean([token.vector for token in female_words], axis=0)
gender_axis = male - female
# Karrierer at teste
careers = ["læge", "sygeplejerske", "ingeniør", "lærer",
"direktør", "sekretær", "pilot", "stewardesse"]
career_gender_scores = {}
for career in careers:
career_vector = nlp(career)[0].vector
# Projicér på køns-akse (positiv = mandlig association)
score = np.dot(career_vector, gender_axis)
career_gender_scores[career] = scoreprint("Kønsassociationer (positiv = mandlig, negativ = kvindelig):")
for career, score in sorted(career_gender_scores.items(), key=lambda x: x[1], reverse=True):
print(f"{career:15s}: {score:8.2f}")Kønsassociationer (positiv = mandlig, negativ = kvindelig):
direktør : 121.98
ingeniør : 102.63
pilot : 63.46
stewardesse : 63.46
sekretær : 14.40
lærer : -13.28
læge : -38.33
sygeplejerske : -78.60
Geometrisk fortolkning:
Word embeddings placerer ord i et 300-dimensionelt rum
Ord der bruges i lignende kontekster ligger tæt på hinanden
Køns-aksen er en linje gennem dette rum der går fra “kvindelige” ord til “mandlige” ord
Score = hvor langt en karriere projiceres langs denne akse
Sproglig fortolkning:
Scoren afspejler hvor ofte karrieren optræder sammen med kønnede ord i træningsdataene:
ingeniør : 152.78
direktør : 143.77
pilot : 89.93
stewardesse : 89.93 ← INTERESSANT!?
Dette afslører bias i træningsdataene, ikke en sandhed …
spaCy indeholder forskellige sprogmodeller—herunder en dansk sprogmodel. SpaCy’s sprogmodeller indeholder blandt andet:
Tokenizer (inddeling i enkeltord)Lemmatizer (konvertering til navneform)Part-Of-Speech tagging (POS-tagging) (identificering af ordtyper)Dependency parsing (sætningskonstruktion)Named-Entity-Recognition (NER) (udledning af “named entities”, fx personer og organisationer)SpaCy’s større modeller (medium og large) kommer med pretrained word vectors. Disse er typisk trænet på enorme korpera (milliarder af tokens) ved hjælp af algoritmer som Word2Vec, GloVe eller FastText.
import spacy
import numpy as np
# Load en model med word vectors
# python3 -m spacy download en_core_web_md
nlp = spacy.load("en_core_web_md")
# Få vektoren for et ord
doc = nlp("dog cat astronaut")
dog_vector = doc[0].vector # En numpy array af dimensionalitet 300
print(f"Vektordimension: {len(dog_vector)}")
print(f"All komponenter: {dog_vector}")Vektordimension: 300
All komponenter: [-0.72483 0.42538 0.025489 -0.39807 0.037463 -0.29811
-0.28279 0.29333 0.57775 1.2205 -0.27903 0.80879
-0.71291 0.045808 -0.46751 0.55944 0.42745 0.58238
0.20854 -0.42718 -0.40284 -0.048941 0.1149 -0.6963
-0.03338 0.052596 -0.22572 -0.35996 0.47961 -0.38386
-0.73837 0.1718 0.52188 0.45584 -0.026621 0.48831
0.67996 -0.73345 -0.27078 0.41739 0.1947 0.27389
-0.70931 -0.45317 -0.22574 -0.12617 0.03268 0.142
0.53923 -0.61285 -0.5322 0.19479 0.13889 -0.020284
0.088162 0.85337 0.039407 0.11529 -0.42646 0.74832
0.34421 -0.59462 0.0040537 0.027203 -0.063394 0.26538
0.34757 0.21395 -0.39799 -0.027067 -0.36132 0.31979
0.55813 -0.5652 0.55382 0.03928 -0.26933 -0.14705
0.74032 -0.50566 0.023765 0.62273 -0.79388 -0.25165
0.11992 -0.43056 1.0614 0.58571 0.8856 -0.056054
0.055826 0.30485 0.64639 -0.43831 -0.45706 0.036471
-0.3466 -0.56219 0.28105 -0.33758 -0.041398 0.22171
0.05262 0.18113 0.65646 -0.56217 0.038915 -0.30335
0.05051 -0.2354 0.3233 0.31744 0.52453 -0.47154
0.13152 -0.15104 0.14265 -0.20747 0.060413 -0.030342
-0.092883 0.80421 -0.12497 -0.56199 0.29128 -0.22488
0.30282 -0.0045144 -0.12305 0.20396 -0.32202 -0.11409
-0.37613 0.40457 0.21461 0.25741 -0.36489 0.94135
0.42725 0.022925 -1.8699 -0.76035 0.73771 0.36998
0.50214 -0.30617 -0.26526 0.86573 0.3808 0.14754
0.29932 -0.078863 -0.28992 -0.064636 -0.68914 0.19527
-0.56368 0.26251 -0.52171 -1.0703 0.42478 -0.0067289
-0.28591 -0.77831 0.049342 0.66675 -0.077419 -0.19226
0.12721 -0.18844 0.13647 0.38804 0.21917 -0.24192
-0.13465 0.23119 -0.43197 0.48302 0.3598 1.128
0.019894 -0.10861 -0.13515 -0.34137 -0.36379 0.080616
0.28682 -0.045819 -0.12114 -0.44835 -0.054611 -0.10362
0.010954 -0.60063 -0.46665 0.15115 -0.31815 -0.58903
1.1325 0.04406 -0.92863 0.3399 -0.03463 -0.40474
0.17245 -0.19983 -0.095982 -0.074758 0.57472 0.25455
-0.20387 0.055758 -0.65017 0.72629 -0.51083 0.11196
0.44724 0.16157 -0.34571 0.19227 -0.063871 0.0057351
0.48703 -0.53762 -0.73398 -0.11488 0.073723 0.58191
0.33192 -0.13303 -0.3478 -0.022676 -0.32494 -0.26496
0.56275 0.098558 -0.16671 -0.40481 0.55477 -0.58692
-0.60433 -0.4227 -0.53712 0.2994 0.11339 -0.3154
-0.28685 0.43999 0.013623 0.011139 -0.47734 -0.01492
0.52524 0.53583 0.36626 0.23119 -0.1386 0.35374
-0.27448 0.066183 0.6224 -0.24851 -0.36066 0.009084
-0.58148 0.24371 0.29944 -0.025314 -0.73222 0.33236
-0.40339 0.82624 0.006984 0.26737 -0.27695 -0.09713
-0.015736 0.1024 -0.026831 -0.26293 0.31401 0.01051
-0.048451 -0.74571 0.75827 0.67771 0.054738 -0.23325
0.17996 -0.206 0.019095 -0.34283 -0.58602 0.0095634
-0.085052 0.83312 0.31978 0.050317 -0.23159 0.28165 ]
# Beregn semantisk lighed
dog = nlp("dog")
cat = nlp("cat")
astronaut = nlp("astronaut")
print(f"dog <-> cat: {dog.similarity(cat)}") # Høj (højt er typisk større end ~0.8)
print(f"dog <-> astronaut: {dog.similarity(astronaut)}") # Lav (lavt er typisk mindre end ~0.2)dog <-> cat: 1.0000001192092896
dog <-> astronaut: 0.12654462456703186
Når vi arbejder med SpaCy, arbejder du fundamentalt med en pipeline.
Når modellen anvendes på et stykke tekst, kan de forskellige værdier og information, som er udledt af teksten, tilgås som attributes (et attribute for token, et for lemma, et for part-of-speech-tag osv.).
Forestil jer en tekststreng, der bevæger sig gennem en række behandlingsstadier, hvor hvert stadie tilføjer et lag af lingvistisk forståelse.
Pipeline-arkitekturen starter med råtekst og sender den gennem en række komponenter i en fastlagt rækkefølge.
Hele denne pipeline er modulariseret og konfigurbar. Du kan tilføje dine egne komponenter, fjerne dem du ikke har brug for, eller ændre rækkefølgen.
Doc-objektet er SpaCy’s fundamentale enhed. Når vi sender en tekst gennem SpaCy’s pipeline, får vi et Doc-objekt tilbage. Dette er en sekvens af tokens med al den lingvistiske information, pipelinenen har tilføjet. (Det er smart fordi det er hukommelseseffektivt…)
Span-objekter repræsenterer sammenhængende sekvenser af tokens. En sætning er en span, en navngiven entitet er en span, en frase er en span.Sprogmodellerne er ikke inkluderet i basisinstallationen, men downloades separat. Dette designvalg handler om at modeller kan være hundredvis af megabytes store, og forskellige brugere har brug for forskellige sprog og præcisionsniveauer.
https://spacy.io/models
python3 -m spacy download en_core_web_md
SpaCy tilbyder typisk modeller i tre størrelser for hvert sprog.
Når SpaCy er installeret, starter vi med at importere biblioteket og indlæse vores sprogmodel. Koden ser således ud:
Dette nlp-objekt er nu vores pipeline.
“Bag scenen” har SpaCy nu initialiseret alle pipeline-komponenter, indlæst de neurale netværk, og er klar til at behandle tekst. Objektet hedder traditionelt nlp, en konvention der gør kode på tværs af projekter let at læse.
da_core_news_sm/
├── config.cfg # Konfiguration
├── meta.json # Metadata
├── tokenizer # Tokenization regler
│ └── exceptions.json
├── vocab/
│ ├── strings.json # Ord → ID mapping
│ ├── vectors # Word embeddings
│ │ └── vectors.bin #
│ └── lexemes.bin # Ordfunktioner
├── tagger/
│ ├── model # POS tagger vægte
│ │ └── model.bin
│ └── cfg # Hyperparametre
├── parser/
│ ├── model # Dependency parser vægte
│ │ └── model.bin
│ └── cfg
├── ner/
│ ├── model # NER vægte
│ │ └── model.bin
│ └── cfg
└── lemmatizer/
└── lookups.bin # Lemmatization tabel
import spacy
nlp = spacy.load("da_core_news_sm")
# Din tekst
tekst = "Regeringen øgede velfærdsudgifterne betydeligt."
doc = nlp(tekst)
# For hvert token kan du se dets ID
for token in doc:
print(f"Ord: '{token.text}'")
print(f" Hash ID: {token.orth}") # Det faktiske ID SpaCy bruger
print(f" Vocab entry: {nlp.vocab.strings[token.orth]}")
print(f" Lemma ID: {token.lemma}")
print(f" POS ID: {token.pos}")
print()Ord: 'Regeringen'
Hash ID: 3849404314323585877
Vocab entry: Regeringen
Lemma ID: 17039087236279372374
POS ID: 92
Ord: 'øgede'
Hash ID: 1409674694569110436
Vocab entry: øgede
Lemma ID: 1540739943586306153
POS ID: 100
Ord: 'velfærdsudgifterne'
Hash ID: 14684542281918508293
Vocab entry: velfærdsudgifterne
Lemma ID: 8400297438331790641
POS ID: 92
Ord: 'betydeligt'
Hash ID: 12616862253303509379
Vocab entry: betydeligt
Lemma ID: 12616862253303509379
POS ID: 86
Ord: '.'
Hash ID: 12646065887601541794
Vocab entry: .
Lemma ID: 12646065887601541794
POS ID: 97
# Processer en tekst gennem pipeline'n
doc = nlp("Mette Frederiksen forklarede beslutningen til journalisterne.")
# Undersøg tokenization
for token in doc:
print(f"Token: {token.text}, Lemma: {token.lemma_}, POS: {token.pos_}")Token: Mette, Lemma: Mette, POS: PROPN
Token: Frederiksen, Lemma: Frederiksen, POS: PROPN
Token: forklarede, Lemma: forklare, POS: VERB
Token: beslutningen, Lemma: beslutning, POS: NOUN
Token: til, Lemma: til, POS: ADP
Token: journalisterne, Lemma: journalist, POS: NOUN
Token: ., Lemma: ., POS: PUNCT
For hvert token får vi originalformen, lemmaet (grundformen), og ordklassen. “besøgte” får lemmaet “besøge”, og POS-tagget VERB, osv …
Bemærk at attributter med en underscore-endelse returnerer strenge, mens versioner uden underscore returnerer ID’er.
# Undersøg dependency-strukturen
for token in doc:
print(f"{token.text} <- {token.dep_} <- {token.head.text}")Mette <- nsubj <- forklarede
Frederiksen <- flat <- Mette
forklarede <- ROOT <- forklarede
beslutningen <- obj <- forklarede
til <- case <- journalisterne
journalisterne <- obl <- forklarede
. <- punct <- forklarede
nsubj: “Mette” er nominal subjekt til “forklarede” (hvem udfører handlingen)flat: “Frederiksen” er flad relation til “Mette” (navn-konstruktion)ROOT: “forklarede” er sætningens rod (hovedverbet)obj: “beslutningen” er direkte objekt for “forklarede” (hvad blev forklaret)case: “til” er præposition der markerer relationen til “journalisterne”obl: “journalisterne” er oblique argument til “forklarede” (til hvem)punct: “.” er punktumEntitet: Mette Frederiksen, Type: PER
SpaCy vil identificere “Mette Frederiksen” som en person, men kan også genkende organisationer.
tekst = """
Statsminister Mette Frederiksen mødtes i går med EU-kommissionen i Bruxelles.
Danske Bank og Novo Nordisk annoncerede et samarbejde om grøn finansiering.
Udenrigsminister Lars Løkke Rasmussen kommenterede, at Danmark vil investere
25 milliarder kroner i klimaprojekter. Folketinget stemte med 90 stemmer for forslaget.
"""
doc = nlp(tekst)
# Udtræk organisationer og personer
organisationer = [ent.text for ent in doc.ents if ent.label_ == "ORG"]
personer = [ent.text for ent in doc.ents if ent.label_ == "PER"]
lokationer = [ent.text for ent in doc.ents if ent.label_ == "LOC"]
print("\n")
print(f"Organisationer fundet: {organisationer}")
print(f"Personer fundet: {personer}")
print(f"Lokationer fundet: {lokationer}")
# Find alle tal med deres kontekst
for token in doc:
if token.like_num:
kontekst = doc[max(0, token.i-3):min(len(doc), token.i+4)]
print(f"Tal: {token.text}, Kontekst: {kontekst.text}")
Organisationer fundet: ['Danske Bank', 'Novo Nordisk', 'Folketinget']
Personer fundet: ['Mette Frederiksen', 'Lars Løkke Rasmussen']
Lokationer fundet: ['Bruxelles', 'Danmark']
Tal: et, Kontekst: Novo Nordisk annoncerede et samarbejde om grøn
Tal: 25, Kontekst: vil investere
25 milliarder kroner i
Tal: 90, Kontekst: Folketinget stemte med 90 stemmer for forslaget
Dette eks. fremhæver SpaCy’s “evne” til mønstergenkendelse og informationsudtræk.